/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/

#ifndef __NU_VARIANT_H__
#define __NU_VARIANT_H__


/* -------------------------------------------------------------------------- */

#include "nu_stdtype.h"
#include "nu_variable.h"
#include "nu_exception.h"
#include "nu_error_codes.h"

#include <memory>
#include <string>
#include <cassert>
#include <map>
#include <iostream>
#include <vector>
#include <list>
#include <functional>

/* -------------------------------------------------------------------------- */

namespace nu
{


/* -------------------------------------------------------------------------- */

class variant_data_t
{
public:
   using type_t = variable_t::type_t;

protected:
   void* _ptr = nullptr;
   type_t _type = type_t::UNDEFINED;

public:
   inline type_t get_type() const throw()
   {
      return _type;
   }

   variant_data_t() = default;
   variant_data_t(type_t t);
   variant_data_t(const variant_data_t & other);
   variant_data_t(variant_data_t && other);
   variant_data_t& operator=(variant_data_t && other);
   variant_data_t& operator=(const variant_data_t & other);

   inline ~variant_data_t() throw()
   {
      free_mem();
   }

   template <typename T>
   explicit variant_data_t(const T& value, type_t t) :
      _ptr(new T(value)), _type(t)
   {
   }

   friend variant_data_t operator+(
      const variant_data_t & a, const variant_data_t & b);

   friend variant_data_t operator-(
      const variant_data_t & a, const variant_data_t & b);

   friend variant_data_t operator*(
      const variant_data_t & a, const variant_data_t & b);

   friend variant_data_t operator/(
      const variant_data_t & a, const variant_data_t & b);

   bool operator<(const variant_data_t & b) const;

   bool operator>=(const variant_data_t & b) const
   {
      return !(*this<b);
   }

   bool operator>(const variant_data_t & b) const
   {
      return b < *this;
   }

   bool operator<=(const variant_data_t & b) const
   {
      return !(*this>b);
   }

   bool operator!=(const variant_data_t & b) const
   {
      return (*this < b) || (b < *this);
   }

   bool operator==(const variant_data_t & b) const
   {
      return !(*this != b);
   }

   bool invert_sign();
   bool invert();
   bool increment();
   bool decrement();

   void set(const variant_data_t& other);
   void cast_set(const variant_data_t& other);

   string_t& string_value()
   {
      return get_item<string_t, type_t::STRING>();
   }

   int_t& int_value()
   {
      return get_item<int_t, type_t::INTEGER>();
   }

   byte_t& byte_value()
   {
      return get_item<byte_t, type_t::BYTE>();
   }

   bool_t& bool_value()
   {
      return get_item<bool_t, type_t::BOOLEAN>();
   }

   double_t& double_value()
   {
      return get_item<double_t, type_t::DOUBLE>();
   }

   float_t& float_value()
   {
      return get_item<float_t, type_t::FLOAT>();
   }

   const string_t& string_value() const
   {
      return get_item<string_t, type_t::STRING>();
   }

   const int_t& int_value() const
   {
      return get_item<int_t, type_t::INTEGER>();
   }

   const byte_t& byte_value() const
   {
      return get_item<byte_t, type_t::BYTE>();
   }

   const bool_t& bool_value() const
   {
      return get_item<bool_t, type_t::BOOLEAN>();
   }

   const double_t& double_value() const
   {
      return get_item<double_t, type_t::DOUBLE>();
   }

   const float_t& float_value() const
   {
      return get_item<float_t, type_t::FLOAT>();
   }

   string_t to_string() const;
   int_t to_int() const;
   byte_t to_byte() const;
   bool_t to_bool() const;
   double_t to_double() const;
   float_t to_float() const;

   inline bool is_numeric() const throw()
   {
      switch (_type)
      {
         case type_t::STRING:
         case type_t::UNDEFINED:
            return false;

         default:
            break;
      }

      return true;
   }

   template<typename T, variable_t::type_t type>
   void set_item(const T& item)
   {
      check_type(type);

      rt_error_code_t::get_instance().throw_if(
         _ptr == nullptr,
         0,
         rt_error_code_t::E_TYPE_ILLEGAL,
         "");

      T* v = (T*)_ptr;
      *v = item;
   }

private:
   void free_mem();
   void check_type(variable_t::type_t t) const;

   template<typename T> inline static
   T* build()
   {
      auto ptr = new T();
      assert(ptr);
      return ptr;
   }

   template<typename T>
   inline static T* copy(const variant_data_t& other)
   {
      auto ptr = build<T>();
      *ptr = *(reinterpret_cast<T*>(other._ptr));

      return ptr;
   }

   template<typename T, variable_t::type_t type>
   inline T& get_item()
   {
      check_type(type);
      return *((T*)_ptr);
   }

   template<typename T, variable_t::type_t type>
   inline const T& get_item() const
   {
      check_type(type);
      return *((T*)_ptr);
   }
};


/* -------------------------------------------------------------------------- */

/**
 * A variant is a special data type that can contain any kind of typed data
 * Numeric data can be any integer (32 bit) or real number value (float range)
 * Variant data type is used to work with data in a more flexible way.
 * This approach simplifies data manipulation logic of the interpreter.
 */
class variant_t
{
public:
   using handle_t = std::shared_ptr < variant_t > ;
   using ptr_t = variant_t*;
   using type_t = variable_t::type_t;

   static bool is_integer_literal(const std::string& value);
   static bool is_float_literal(const std::string& value);

   variant_t(const string_t& value, type_t t) throw();
   variant_t(const variant_data_t& value) throw();
   variant_t(const string_t& value) throw();
   variant_t(const float_t& value) throw();
   variant_t(const double_t& value) throw();
   variant_t(const int_t& value) throw();
   variant_t(const bool_t& value) throw();
   variant_t(const byte_t& value) throw();

   variant_t(ptr_t ref) throw() : _ref_value(ref) {}

   variant_t() = default;

   variant_t(const variant_t& v);
   variant_t& operator =(const variant_t& v);
   variant_t(variant_t&& v);
   variant_t& operator=(variant_t&& v);


   bool is_const() const throw()
   {
      return _ref_value ? _ref_value->is_const() : _constant;
   }

   bool is_reference() const throw()
   {
      return _ref_value != nullptr;
   }

   float_t to_float() const;
   double_t to_double() const;
   int_t to_int() const;
   bool_t to_bool() const;
   bool_t to_byte() const;

   type_t get_type() const
   {
      return _ref_value ?
             _ref_value->get_type() :
             (_data_ptr ?
              _data_ptr->get_type() : type_t::UNDEFINED);
   }


   explicit operator float_t() const
   {
      return _ref_value ? _ref_value->to_float() : to_float();
   }


   explicit operator double_t() const
   {
      return _ref_value ? _ref_value->to_double() : to_double();
   }


   explicit operator int_t() const
   {
      return _ref_value ? _ref_value->to_int() : to_int();
   }

   string_t to_string() const;


   explicit operator string_t() const
   {
      return _ref_value ? _ref_value->to_string() : to_string();
   }


   explicit operator bool() const
   {
      return _ref_value ? _ref_value->to_bool() : to_bool();
   }


   inline void set(const string_t& value)
   {
      set_item<string_t, type_t::STRING>(value);
   }

   inline void set(const int_t& value)
   {
      set_item<int_t, type_t::INTEGER>(value);
   }

   inline void set(const bool_t& value)
   {
      set_item<bool_t, type_t::BOOLEAN>(value);
   }

   inline void set(const float_t& value)
   {
      set_item<float_t, type_t::FLOAT>(value);
   }

   inline void set(const double_t& value)
   {
      set_item<double_t, type_t::DOUBLE>(value);
   }

   //void set_bvect(const bvec_t & value);
   bool is_numeric() const throw();
   bool set(const variant_t& other);

   void set_const(bool enable) throw()
   {
      if (_ref_value)
         _ref_value->_constant = enable;

      else
         _constant = enable;
   }

   bool cast_to( type_t new_type );

   friend variant_t operator +(const variant_t& a, const variant_t& b);
   friend variant_t operator -(const variant_t& a, const variant_t& b);
   friend variant_t operator *(const variant_t& a, const variant_t& b);
   friend variant_t operator /(const variant_t& a, const variant_t& b);

   void increment();
   void decrement();

   bool operator<(const variant_t & b) const;
   bool operator==(const variant_t & b) const;

   inline bool operator>=(const variant_t & b) const
   {
      return !(*this<b);
   }

   inline bool operator>(const variant_t & b) const
   {
      return (b<(*this));
   }

   inline bool operator<=(const variant_t & b) const
   {
      return !((*this)>b);
   }

   inline bool operator!=(const variant_t & b) const
   {
      return !((*this)==b);
   }


   variant_t int_mod(const variant_t& b) const;
   variant_t power(const variant_t&b) const;

   variant_t& operator+=(const variant_t&b);
   variant_t& operator-=(const variant_t&b);

   friend std::ostream& operator << (std::ostream& os, const variant_t& val);

   variant_t operator[](size_t idx) const;

   static const char* get_type_desc(const type_t& type) throw();

protected:
   bool _constant = false;
   ptr_t _ref_value = nullptr;
   std::unique_ptr< variant_data_t > _data_ptr;

   template <typename T, type_t type>
   void build(const T& value)
   {
      _data_ptr =
         std::make_unique< variant_data_t >(value, type);
   }

   template <typename T, type_t type>
   void set_item(const T& value)
   {
      if (_ref_value)
      {
         _ref_value->set_item<T, type>(value);
      }

      else
      {
         rt_error_code_t::get_instance().throw_if(
            is_const(),
            0,
            rt_error_code_t::E_CANNOT_MOD_CONST,
            "");

         if (!_data_ptr || _data_ptr->get_type() == type_t::UNDEFINED)
            _data_ptr = std::move(
                           std::make_unique< variant_data_t >(type));

         _data_ptr->set_item<T, type>(value);
      }
   }


   /* -------------------------------------------------------------------------- */

};


/* -------------------------------------------------------------------------- */

}


/* -------------------------------------------------------------------------- */

#endif // __NU_VARIANT_H__
